<?php
class App_Module_Media_Component_Style_Ctrl_Http extends Mozg_Controller_Instance
{

	protected $_styleFiles = array();
	
    protected function ControllerInit ()
    {
		$this->_styleFiles = Mozg_Cfg::Get('global')->GetStyleFiles();
    }
    
    protected function PrimaryActionInit ($basicActionName)
    {
     
    }

    public function PrAction()
    {
        $this->archive_stylePr();
    }

    /**
		перехватывает обращение к css или js файлу, минимизирует и гзипит его
	*/
    public function archive_stylePrAction()
    {
        list($requestUri) = explode('?', Mozg::Request('http')->getRequestUri());
        $filePath = ltrim($requestUri, '/');
        
        $pathParts = pathinfo($filePath);
        
        if (!Mozg::Lib('Dir')->TestExtension($filePath, Mozg_Cfg::Get('global')->GetStyleExtensions()))
        {
        	throw new Mozg_Exception_NotFound();
        }
        
        if (!is_file(_ROOT_PATH.$filePath) && !$this->CreateMergedStyleFile(_ROOT_PATH.$filePath))
        {
        	throw new Mozg_Exception_NotFound();
        }
    
        $archFolder = _CACHE_PATH_STYLE;
        
        Mozg::Response('http')->TurnCompression(true);
        
        $isCompression = Mozg::Response('http')->IsCompressionSupported();
        
        if ($isCompression) $archFolder .= 'gz/';
        else $archFolder .= 'min/';
        
        if ($isCompression)
        {
        	$gzExtensions = Mozg_Cfg::Get('global')->GetGzExtensions();
            $newFilePath = $archFolder.$pathParts['dirname'].'/'.$pathParts['filename'].'.'.$gzExtensions[strtolower($pathParts['extension'])];
        }
        else
        {
            $newFilePath = $archFolder.$filePath;
        }
        
        if (!Mozg::Lib('dir')->Create($archFolder.$pathParts['dirname']))
        {
            throw new Mozg_Exception_NotFound();
        }

        Mozg::Response('http')->SetBody($this->GetDynamicContent(_ROOT_PATH.$filePath));

        file_put_contents($newFilePath, Mozg::Response('http')->GetResponse());
        @chmod($newFilePath, _CHMOD_FILE);

        $fileType = $this->GetTypeByExt($pathParts['extension']);
        
        Mozg::Layout('media')->SetTemplate($fileType);
    }

    private function GetTypeByExt($ext)
    {
        switch (strtolower($ext))
        {
			case 'css' :
				$MinifyFunction = 'CSS';
        		break;
        	case 'js' :
				$MinifyFunction = 'JS';
        		break;
        		
        	case 'htm':
        	case 'html':
				$MinifyFunction = 'HTML';
        		break;
        		
        	default:
        		$MinifyFunction = 'HTML';
        }

        return $MinifyFunction;
    }

    private function MynifyContent($filePath)
    {
    	$pathParts = pathinfo($filePath);
		$fileType = $this->GetTypeByExt($pathParts['extension']);

        $MinifyFunction = 'Minify' .$fileType;

        $fileContent = $this->GetDynamicContent(_ROOT_PATH.$filePath);

        $count = 0;
        $fileContent = str_replace('/*skip minifier*/', '', $fileContent, $count);
        if (!$count)
        {
        	$content = Mozg::Lib('minifier')->$MinifyFunction($fileContent);
        }
        else
        {
        	$content = $fileContent;
        }
        
        return $content;
    }
    
    private function GetDynamicContent($filePath)
    {
        $t = $this->GetTpl('Web')->CacheTurnOff();
        return $t->RenderStyle($filePath);
    }

    /**
		содержит номера ревизий файлов стилей(js, css)
	*/
    private $_styleRevisions = array();
    private $_inRevision = array('css', 'js');
    private $_startRevision = 1;
    
    /**
		возвращает номер ревизии
	*/
    public function GetStyleRevision($type)
    {
        $this->LoadStyleRevision();
        if (!isset($this->_styleRevisions[$type])) throw new Mozg_Exception_System(Mozg_Lang::_('UNKNOWN_FILE_TYPE'));
        return $this->_styleRevisions[$type];
    }
    
    private $_isRevisionUpdated = false;
    
    /**
		увеличивает номер ревизии на единицу
	*/
    public function UpdateStyleRevision($type)
    {
        $this->LoadStyleRevision();
        if (!isset($this->_styleRevisions[$type])) throw new Mozg_Exception_System(Mozg_Lang::_('UNKNOWN_FILE_TYPE'));
        $this->_styleRevisions[$type]++;
        $this->_isRevisionUpdated = true;
    }
    
    private $_isRevisionLoaded = false;
    
    /**
		загружает текущие номера ревизий
	*/
    private function LoadStyleRevision()
    {
        if ($this->_isRevisionLoaded) return;
        $this->_isRevisionLoaded = true;
        
        $revFile = _CACHE_CONFIG_PATH. '_style_revisions';
        if (Mozg_Storage::Exists($revFile)) $this->_styleRevisions = Mozg_Storage::Read($revFile);

        $this->_styleRevisions = $this->InitStyleRevision($this->_styleRevisions);
    }
    
    /**
		сохраняет номера ревизий в постоянном хранилище
	*/
    private function SaveStyleRevision()
    {
        if (!$this->_isRevisionUpdated) return;
        
        $revFile = _CACHE_CONFIG_PATH. '_style_revisions';
        Mozg_Storage::Save($revFile, $this->_styleRevisions);
    }
    
    private function InitStyleRevision($currentRevisions = array())
    {
        $revArr = array_flip($this->_inRevision);
        foreach ($revArr as $k=>&$v)
        {
            $v = isset($currentRevisions[$k]) ? (int)$currentRevisions[$k] : $this->_startRevision;
        }
        return $revArr;
    }

    private function SetStyleFileRevision($path, $type)
    {
        $this->LoadStyleRevision();
        if (!isset($this->_styleRevisions[$type])) throw new Mozg_Exception_System(Mozg_Lang::_('UNKNOWN_FILE_TYPE'));
        list($path, $query) = explode('?', $path, 2);
		$pathInfo = pathinfo($path);
        return $pathInfo['dirname']. '/' .$pathInfo['filename']. '__' .$this->_styleRevisions[$type]. '__.' .$pathInfo['extension'].(strlen($query) ? '?' .$query : '');
    }
    
    static $_startLabelTag = false;
    
    private function GetStartLabelTag()
    {
    	if (!self::$_startLabelTag)
    	{
    		self::$_startLabelTag = Mozg_Cfg::Get('global')->GetUniqueToken();
    	}
    	
    	return self::$_startLabelTag;
    }
    
    private function GetEndLabelTag()
    {
    	return $this->GetStartLabelTag();
    }
    
    private function GetStyleFileLabel($type, $code, $placeholderName, $isStaticDomain = true, $isCountRevision = true)
    {
    	if (!is_array($code)) $code = array($code);
    	if (!sizeof($code)) return '';
    	
    	return '<style_inc' .$this->GetStartLabelTag(). '|' .$placeholderName. '::' .$type. '::' .($isStaticDomain ? 'static' : 'main'). '::' .($isCountRevision ? '1' : '0'). '::' .implode('-', $code). '|' .$this->GetEndLabelTag(). '/>';
    }

    public function GetStylePlaceholder($type, $name = 'default')
    {
    	Mozg::Response('http')->RegisterOutputFilter('styleParser', array($this, 'ParseStyleLabels'));
    	return '<style_placeholder' .$this->GetStartLabelTag(). '|' .$name. '::' .$type. '|' .$this->GetEndLabelTag(). '/>';
    }
    
    public function IncludeStyleFile(array $filesByType = null, $placeholderName = false, $isStaticDomain = true, $isCountRevision = true)
    {
    	if (!sizeof($filesByType)) return '';
		if (false === $placeholderName) $placeholderName = 'default';
    	
    	Mozg::Response('http')->RegisterOutputFilter('styleParser', array($this, 'ParseStyleLabels'));

    	$fileLabels = '';
   		foreach ($filesByType as $fType => $fileCode)
   		{
   			$fileLabels .= $this->GetStyleFileLabel($fType, $fileCode, $placeholderName, $isStaticDomain, $isCountRevision);
   		}

   		return $fileLabels;
    }

    private $_includedFiles = array();
    
    public function ParseStyleLabels($content)
    {
    	$this->_includedFiles = array();
    	
        //parse placeholders
        $matchPlaces = array();
    	preg_match_all('#(<|&lt;)style_placeholder' .preg_quote($this->GetStartLabelTag(), '#'). '\|([^|]*?)\|' .preg_quote($this->GetEndLabelTag(), '#'). '(/|\\\/)(>|&gt;)#i', $content, $matchPlaces, PREG_SET_ORDER);
        
    	$placeBlocks = array();
    	foreach ($matchPlaces as $place)
    	{
    		$placeBlocks[$place[2]] = array('match' => $place, 'html' => '');
    	}
    	
    	//parse files
    	$matches = array();
    	preg_match_all('#(<|&lt;)style_inc' .preg_quote($this->GetStartLabelTag(), '#'). '\|([^|]*?)\|' .preg_quote($this->GetEndLabelTag(), '#'). '(/|\\\/)(>|&gt;)#i', $content, $matches, PREG_SET_ORDER);

    	foreach ($matches as $styleBlock)
    	{
    		$pos = strpos($content, $styleBlock[0]);
    		if (false === $pos) continue;
    		
    		list($placeholderName, $fType, $domainType, $isCountRevision, $fCodeList) = explode('::', $styleBlock[2]);
    		$placeholderName .= '::' .$fType;
    		
    		$htmlBlock = $this->GetStyleHTMLBlock($fType, $fCodeList, $domainType == 'static' ? true : false, (boolean)$isCountRevision);
    		
    		if (isset($placeBlocks[$placeholderName]))
    		{
    			$placeBlocks[$placeholderName]['html'] .= $htmlBlock;
				$htmlBlock = '';
    		}
    		else
    		{
	    		if ($styleBlock[4] == '&gt;') $htmlBlock = htmlspecialchars($htmlBlock);
	    		if ($styleBlock[3] == '\\/') $htmlBlock = Mozg::Lib('str')->escapeJSON($htmlBlock);
    		}
    		
    		$content = substr_replace($content, $htmlBlock, $pos, strlen($styleBlock[0]));
    	}
    	
        foreach ($placeBlocks as $place)
    	{
    		$pos = strpos($content, $place['match'][0]);
    		if (false === $pos) continue;
			
    		$htmlBlock = $place['html'];
    		
    		if ($place['match'][4] == '&gt;') $htmlBlock = htmlspecialchars($htmlBlock);
    		if ($place['match'][3] == '\\/') $htmlBlock = Mozg::Lib('str')->escapeJSON($htmlBlock);
    
    		$content = substr_replace($content, $htmlBlock, $pos, strlen($place['match'][0]));
    		$content = str_replace($place['match'][0], '', $content);
    	}
    	
    	return $content;
    }
    
    public function GetStyleFilePath($type, array $fileCodes, $isStaticDomain = true, $isCountRevision = true)
    {
        if (!isset($this->_includedFiles[$type])) $this->_includedFiles[$type] = array();

    	$fileCodes = array_unique($fileCodes);
    	$fileCodes = array_diff($fileCodes, $this->_includedFiles[$type]);
    	$this->_includedFiles[$type] = array_merge($this->_includedFiles[$type], $fileCodes);

    	$realFileCodes = array();
    	foreach ($fileCodes as $fCode)
    	{
    		if (!isset($this->_styleFiles[$type][$fCode])) continue;
    		$realFileCodes[] = $fCode;
    	}

    	if (!sizeof($realFileCodes)) return false;
    	
    	$path = $isStaticDomain ? _ROOT_URL_STATIC : _ROOT_URL;
    	
    	if (!$isCountRevision && sizeof($realFileCodes) == 1)
    	{
    		$path .= $this->_styleFiles[$type][$fCode];
    	}
    	else
    	{
    		$path .= _CACHE_FOLDER_STYLE.implode('-', $realFileCodes). '.' .$type;
    		$path = $this->SetStyleFileRevision($path, $type);
    	}

    	return $path;
    }
    
    private function GetStyleHTMLBlock($type, $fCodeList, $isStaticDomain = true, $isCountRevision = true)
    {
    	$methodName = '';
    	switch ($type)
		{
			case 'css': $methodName = 'IncludeCssFile';
						break;
			case 'js': $methodName = 'IncludeJsFile';
						break;
			default: return '';
		}
    	
    	$fileCodes = explode('-', $fCodeList);
    	
    	$html = '';
    	
    	if ($isCountRevision)
    	{
			$html = $this->$methodName($this->GetStyleFilePath($type, $fileCodes, $isStaticDomain, true));
    	}
    	else
    	{
    		foreach ($fileCodes as $code)
    		{
    			$html .= $this->$methodName($this->GetStyleFilePath($type, array($code), $isStaticDomain, false));
    		}
    	}
    	
		return $html;
    }

    /**
		возвращает <link> блок для css файлов
	*/
    public function IncludeCSSFile($path)
    {
    	if (!$path) return '';
        return '<link rel="stylesheet" type="text/css" href="' .htmlspecialchars($path). '" />';
    }
    
    /**
		возвращает <script> блок для js файлов
	*/
    public function IncludeJSFile($path)
    {
    	if (!$path) return '';
        return '<script type="text/javascript" src="' .htmlspecialchars($path). '"></script>';
    }
    
    private function CreateMergedStyleFile($filePath)
    {
    	$pathParts = pathinfo(preg_replace('#__\d+__\.#is', '.', $filePath));//вырезаем номер ревизии
    	if (($pathParts['dirname']. '/') != _CACHE_PATH_STYLE) return false;
    	
    	$fileCodes = explode('-', $pathParts['filename']);

        $realFileCodes = array();
    	foreach ($fileCodes as $fCode)
    	{
    		if (!isset($this->_styleFiles[$pathParts['extension']][$fCode])) return false;
    		$realFileCodes[] = $fCode;
    	}

    	if (!Mozg::Lib('dir')->Create(_CACHE_PATH_STYLE)) return false;
    	
    	$mergedContent = '';
    	foreach ($realFileCodes as $fCode)
    	{
    		$content = $this->MynifyContent($this->_styleFiles[$pathParts['extension']][$fCode]);
    		$mergedContent .= str_replace("\xEF\xBB\xBF", '', $content). ' ';
    	}
    	
    	file_put_contents($filePath, $mergedContent);
    	@chmod($filePath, _CHMOD_FILE);
    	
    	return true;
    }

    public function __destruct()
    {
        $this->SaveStyleRevision();
    }
}